
/* GCSx
** LAYEREDIT.CPP
**
** Layer editor-only functionality
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

LayerEdit::LayerEdit(WorldEdit* myWorld, Scene* myScene, int myId) : Layer(myWorld, myScene, myId) { start_func
    if (myId) assert(myWorld);

    if (myId) {
        // Find an unused "Untitled" name
        string findName("layer");
        int pos = 1;
        while (myScene->findLayer(findName) >= 0) {
            findName = "layer ";
            findName += intToStr(++pos);
        }
    
        findName[0] = toupper(findName[0]);
        name = findName;
        headerModified = contentModified = 1;
    }
    else headerModified = contentModified = 0;
    
    nameL = name;
    toLower(nameL);

    disassociated = 0;
    desktop->broadcastObjChange(OBJ_LAYER | OBJMOD_CREATE, this, 0, 0);
}

LayerEdit::~LayerEdit() { start_func
    // @TODO: doesn't appear to be needed; no way to delete accidentally
    // if (!disassociated) desktop->broadcastObjChange(OBJ_LAYER | OBJMOD_DELETE, this, 0, 0);
}

void LayerEdit::setInfo(WorldEdit* myWorld, int newId) { start_func
    world = myWorld;
    id = newId;
}

void LayerEdit::disassociate() throw_File { start_func
    // Ensure not cached- our exception point
    scene->cacheLoad();
    
    // Notify objects
    desktop->broadcastObjChange(OBJ_LAYER | OBJMOD_DELETE, this, 0, 0);
    disassociated = 1;
}

void LayerEdit::reassociate() { start_func
    assert(disassociated);

    // Notify objects
    desktop->broadcastObjChange(OBJ_LAYER | OBJMOD_CREATE, this, 0, 0);
    
    // Ensure written to file
    headerModified = contentModified = 1;
    disassociated = 0;
}

SpawnEdit* LayerEdit::getSpawn(int pos) { start_func
    assert(pos >= 0);
    assert(pos < (int)spawns.size());

    return dynamic_cast<SpawnEdit*>(spawns[pos]);
}

void LayerEdit::blitSpawns(int pX, int pY, int pW, int pH, int offsetX, int offsetY, SDL_Surface* dest, const set<int>* selected, int dim) { start_func
    vector<Spawn*>::iterator end = spawns.end();
    for (vector<Spawn*>::iterator pos = spawns.begin(); pos != end; ++pos) {
        SpawnEdit* spawn = dynamic_cast<SpawnEdit*>(*pos);
        int sel = 0;
        if (selected) sel = selected->count(spawn->getId());
        spawn->blit(offsetX, offsetY, dest, pX, pY, pW, pH, dim, sel);
    }
}

void LayerEdit::blit(int pX, int pY, int pW, int pH, int offsetX, int offsetY, SDL_Surface* dest, int dim) { start_func
    if ((layerType == LAYER_TILE) && (tileset)) {
        blitTileLayer(dynamic_cast<TileSetEdit*>(tileset), tileData, usesExtended ? extendedData : NULL, xSize, ySize, pX, pY, pW, pH, offsetX, offsetY, dest, dim);
    }
}

// doesn't get passed or support effects layer
void LayerEdit::blitTileLayer(const TileSetEdit* tileset, const Uint32* data, const Uint32* dataExt, int width, int height, int pX, int pY, int pW, int pH, int offsetX, int offsetY, SDL_Surface* dest, int dim, const Uint32* selData, const Uint32* selDataExt, int selXOffs, int selYOffs, int selWidth) { start_func
    if (pX < 0) {
        pW += pX;
        pX = 0;
    }
    if (pY < 0) {
        pH += pY;
        pY = 0;
    }
    if (pW <= 0) return;
    if (pH <= 0) return;

    int tileWidth = tileset->getWidth();
    int tileHeight = tileset->getHeight();

    // Determine first/last rows to show
    int tY = pY / tileHeight;
    int bottom = (pY + pH - 1) / tileHeight;
    if (bottom >= height) bottom = height - 1;
    if (tY > bottom) return;

    // Determine first/last tiles of each row we need to show
    int leftmost = pX / tileWidth;
    int rightmost = (pX + pW - 1) / tileWidth;
    if (rightmost >= width) rightmost = width - 1;
    if (leftmost > rightmost) return;
    
    // Determine the starting x/y pixel
    pX = offsetX + leftmost * tileWidth;
    pY = offsetY + tY * tileHeight;
    
    data = data + leftmost + tY * width;
    if (dataExt) dataExt = dataExt + leftmost + tY * width;
    if (selData) {
        selData = selData + leftmost - selXOffs + (tY - selYOffs) * width;
        if (selDataExt) selDataExt = selDataExt + leftmost - selXOffs + (tY - selYOffs) * width;
    }
    int pitch = width - rightmost + leftmost - 1;
    for (; tY <= bottom; ++tY) {
        for (int tX = leftmost; tX <= rightmost; ++tX) {
            // Draw tile
            // @TODO: animate?, collision?
            Uint32 dat = *data;
            Uint32 ext = LAYER_EXT_DEFAULT;
            if (dataExt) ext = *dataExt;
            
            // Apply floating selection if present, and within proper range
            if (selData) {
                if ((tX < width + selXOffs) && (tX >= selXOffs) && (tY < height + selYOffs) && (tY >= selYOffs)) {
                    if (*selData & LAYER_TILE_INDEX) {
                        dat = *selData;
                        if (selDataExt) ext = *selDataExt;
                    }
                }
            }

            int tile = dat & LAYER_TILE_INDEX;
            if (tile) {
                int alpha = ext & LAYER_EXT_ALPHA;
                alpha = alpha >> LAYER_EXT_ALPHA_SHIFT;
                if (dim) alpha = alpha / 2 + 1;
                
                tileset->blitTileFx(tile, dest, pX, pY, alpha,
                                    dat & LAYER_TILE_MIRROR,
                                    dat & LAYER_TILE_FLIP,
                                    dat & LAYER_TILE_ROTATE,
                                    (dat & LAYER_TILE_COLOR) >> LAYER_TILE_COLOR_SHIFT);
            }

            pX += tileWidth;
            ++data;
            if (dataExt) ++dataExt;
            if (selData) ++selData;
            if (selDataExt) ++selDataExt;
        }
        
        pX = offsetX + leftmost * tileWidth;
        pY += tileHeight;
        data += pitch;
        if (dataExt) dataExt += pitch;
        if (selData) selData += pitch;
        if (selDataExt) selDataExt += pitch;
    }
}

void LayerEdit::setHeaderModified() { start_func
    getSceneEdit()->setHeaderModified();
    headerModified = 1;
}

void LayerEdit::setContentModified() { start_func
    getSceneEdit()->setContentModified();
    contentModified = 1;
}

void LayerEdit::loadLayer(Uint32* dest, Uint32* destExt, Uint32* destFx, int destPitch, const Rect& rect) const { start_func
    assert(layerType == LAYER_TILE);
    assert(rect.x >= 0);
    assert(rect.y >= 0);
    assert(rect.x + rect.w <= xSize);
    assert(rect.y + rect.h <= ySize);
    assert(dest);
    assert(destPitch >= rect.w);
    assert(!cached);
    
    matrixCopy(tileData + rect.x + rect.y * xSize, dest,
               rect.w * 4, rect.h, xSize * 4, destPitch * 4);

    if ((destExt) && (usesExtended)) {
        matrixCopy(extendedData + rect.x + rect.y * xSize, destExt,
                   rect.w * 4, rect.h, xSize * 4, destPitch * 4);
    }

    if ((destFx) && (usesEffects)) {
        matrixCopy(effectsData + rect.x + rect.y * xSize, destFx,
                   rect.w * 4, rect.h, xSize * 4, destPitch * 4);
    }
}

void LayerEdit::saveLayer(const Uint32* source, const Uint32* sourceExt, const Uint32* sourceFx, int sourcePitch, const Rect& rect, Window* exWin) { start_func
    assert(layerType == LAYER_TILE);
    assert(rect.x >= 0);
    assert(rect.y >= 0);
    assert(rect.x + rect.w <= xSize);
    assert(rect.y + rect.h <= ySize);
    assert(source);
    assert(sourcePitch >= rect.w);
    assert(!cached);
    
    matrixCopy(source + rect.x + rect.y * sourcePitch, tileData,
               rect.w * 4, rect.h, sourcePitch * 4, xSize * 4);

    if ((sourceExt) && (usesExtended)) {
        matrixCopy(sourceExt + rect.x + rect.y * sourcePitch, extendedData,
                   rect.w * 4, rect.h, sourcePitch * 4, xSize * 4);
    }

    if ((sourceFx) && (usesEffects)) {
        matrixCopy(sourceFx + rect.x + rect.y * sourcePitch, effectsData,
                   rect.w * 4, rect.h, sourcePitch * 4, xSize * 4);
    }

    setContentModified();
    
    // Send out update event
    desktop->broadcastObjChange(OBJ_LAYER | OBJMOD_TILES, this, 0, 0, exWin);
}

void LayerEdit::swapLayer(Uint32* data, Uint32* dataExt, Uint32* dataFx, int dataPitch, const Rect& rect) { start_func
    assert(layerType == LAYER_TILE);
    assert(rect.x >= 0);
    assert(rect.y >= 0);
    assert(rect.x + rect.w <= xSize);
    assert(rect.y + rect.h <= ySize);
    assert(data || dataFx || dataExt);
    assert(dataPitch >= rect.w);
    assert(!cached);
    
    int lineSize = rect.w * 4;
    Uint32* ourData = NULL;
    Uint32* ourDataExt = NULL;
    Uint32* ourDataFx = NULL;
    if (data) ourData = tileData + rect.x + rect.y * xSize;
    if ((dataExt) && (usesExtended)) ourDataExt = extendedData + rect.x + rect.y * xSize;
    if ((dataFx) && (usesEffects)) ourDataFx = effectsData + rect.x + rect.y * xSize;

    // (optimization)
    if ((!ourData) && (!ourDataExt) && (!ourDataFx)) return;
    
    Uint32* temp = new Uint32[rect.w];

    for (int pos = rect.h; pos > 0; --pos) {
        if (ourData) {
            memcpy(temp, data, lineSize);
            memcpy(data, ourData, lineSize);
            memcpy(ourData, temp, lineSize);
            ourData += xSize;
            data += dataPitch;
        }

        if (ourDataExt) {
            memcpy(temp, dataExt, lineSize);
            memcpy(dataExt, ourDataExt, lineSize);
            memcpy(ourDataExt, temp, lineSize);
            ourDataExt += xSize;
            dataExt += dataPitch;
        }

        if (ourDataFx) {
            memcpy(temp, dataFx, lineSize);
            memcpy(dataFx, ourDataFx, lineSize);
            memcpy(ourDataFx, temp, lineSize);
            ourDataFx += xSize;
            dataFx += dataPitch;
        }
    }

    setContentModified();
    
    // Send out update event
    desktop->broadcastObjChange(OBJ_LAYER | OBJMOD_TILES, this, 0, 0);
    delete[] temp;
}

void LayerEdit::setName(const std::string& newName, Window* srcWin, Window* exWin) throw_Undo { start_func
    if (name != newName) {
        setHeaderModified();
        if (world) getWorldEdit()->undo.storeUndoName(UndoBuffer::UNDO_LAYERNAME, id, name, newName, srcWin);
        name = newName;
        nameL = newName;
        toLower(nameL);
    
        desktop->broadcastObjChange(OBJ_LAYER | OBJMOD_NAME, this, 0, 0, exWin);
    }
}

void LayerEdit::setType(LayerType newType, Window* srcWin, Window* exWin) throw_Undo { start_func
    assert(!cached);

    if (newType != layerType) {
        setHeaderModified();
        setContentModified();
        if (world) {
            getWorldEdit()->undo.preUndoBlock();
            // @TODO: This won't store the destroyed tileset/effects/collision settings!
            getWorldEdit()->undo.storeUndoLayerType(id, layerType, srcWin);
        }
    
        layerType = newType;
        if (layerType != LAYER_TILE) {
            if (tileset) {
                for (int pos = 0; pos < lockCount; ++pos) {
                    tileset->markUnlock();
                }
            }
            tilesetId = 0;
            tileset = NULL;
            usesEffects = 0;
            usesExtended = 0;
        }

        // Send this first so items note the changed type before noting sizes
        desktop->broadcastObjChange(OBJ_LAYER | OBJMOD_TYPE, this, 0, 0, exWin);

        // setSize() handles most of the work for us.
        // If throws undo, the previous storeUndoLayerType will undo the above changes
        // @TODO: don't call this if undoing- undo will call it in a separate block
        // Purposely don't pass on exWin as this is a "cascaded" change that may not be expected
        setSize(xSize, ySize, srcWin);
        if (world) getWorldEdit()->undo.postUndoBlock();
    }
}

void LayerEdit::setEffects(int newUsesEffects, Window* srcWin, Window* exWin) throw_Undo { start_func
    assert(!cached);
    assert(layerType == LAYER_TILE);

    usesEffects = usesEffects ? 1 : 0;
    newUsesEffects = newUsesEffects ? 1 : 0;
    if (usesEffects != newUsesEffects) {
        setHeaderModified();
        setContentModified();
        if (world) {
            getWorldEdit()->undo.preUndoBlock();
            getWorldEdit()->undo.storeUndoLayerFx(id, usesEffects, srcWin);
        }

        usesEffects = newUsesEffects;

        // setSize() handles most of the work for us.
        // @TODO: don't call this if undoing- undo will call it in a separate block
        // Purposely don't pass on exWin as this is a "cascaded" change that may not be expected
        setSize(xSize, ySize, srcWin);
        if (world) getWorldEdit()->undo.postUndoBlock();

        desktop->broadcastObjChange(OBJ_LAYER | OBJMOD_USESFX, this, 0, 0, exWin);
    }
}

void LayerEdit::setExtended(int newUsesExtended, Window* srcWin, Window* exWin) throw_Undo { start_func
    assert(!cached);
    assert(layerType == LAYER_TILE);

    usesExtended = usesExtended ? 1 : 0;
    newUsesExtended = newUsesExtended ? 1 : 0;
    if (usesExtended != newUsesExtended) {
        setHeaderModified();
        setContentModified();
        if (world) {
            getWorldEdit()->undo.preUndoBlock();
            getWorldEdit()->undo.storeUndoLayerExt(id, usesExtended, srcWin);
        }

        usesExtended = newUsesExtended;

        // setSize() handles most of the work for us.
        // @TODO: don't call this if undoing- undo will call it in a separate block
        // Purposely don't pass on exWin as this is a "cascaded" change that may not be expected
        setSize(xSize, ySize, srcWin);
        if (world) getWorldEdit()->undo.postUndoBlock();

        desktop->broadcastObjChange(OBJ_LAYER | OBJMOD_USESEXT, this, 0, 0, exWin);
    }
}

void LayerEdit::setSize(int newX, int newY, Window* srcWin, Window* exWin, Uint32** undo1, Uint32** undo2, Uint32** undo3) throw_Undo { start_func
    assert(!cached);
    assert(newX >= 1);
    assert(newY >= 1);
    
    Uint32* newTileData = NULL;
    Uint32* newExtendedData = NULL;
    Uint32* newEffectsData = NULL;

    setHeaderModified();
    setContentModified();

    // Undo method?
    if (undo1) {
        if (layerType == LAYER_TILE) {
            swap(*undo1, tileData);
            swap(*undo3, extendedData);
            swap(*undo2, effectsData);
        }
    }
    else {
        try {
            // @TODO: Allocate space for FONT and IMAGE layers
            
            // Reallocate 
            if (layerType == LAYER_TILE) {
                assert(newX <= MAX_LAYERTILESIZE);
                assert(newY <= MAX_LAYERTILESIZE);
                
                // Don't do anything unless size/effects status changes OR we have a NULL ptr currently
                if ((newX != xSize) || (newY != ySize) || (!tileData) ||
                    ((usesEffects) && (!effectsData)) || ((!usesEffects) && (effectsData)) ||
                    ((usesExtended) && (!extendedData)) || ((!usesExtended) && (extendedData))) {

                    newTileData = new Uint32[newX * newY];
                    memSet32(newTileData, LAYER_TILE_DEFAULT, newX * newY);
                    if (tileData != NULL) matrixCopy(tileData, newTileData, min(newX, xSize) * 4, min(newY, ySize), xSize * 4, newX * 4);

                    if (usesExtended) {
                        newExtendedData = new Uint32[newX * newY];
                        memSet32(newExtendedData, LAYER_EXT_DEFAULT, newX * newY);
                        if (extendedData != NULL) matrixCopy(extendedData, newExtendedData, min(newX, xSize) * 4, min(newY, ySize), xSize * 4, newX * 4);
                    }
                
                    if (usesEffects) {
                        newEffectsData = new Uint32[newX * newY];
                        memSet32(newEffectsData, LAYER_FX_DEFAULT, newX * newY);
                        if (effectsData != NULL) matrixCopy(effectsData, newEffectsData, min(newX, xSize) * 4, min(newY, ySize), xSize * 4, newX * 4);
                    }
    
                    // Store undo (instead of deletion) - exception point
                    if (world) {
                        getWorldEdit()->undo.storeUndoLayerSize(id, xSize, ySize, tileData, extendedData, effectsData, srcWin);
                    }
                    else {
                        delete[] tileData;
                        delete[] extendedData;
                        delete[] effectsData;
                    }
                    tileData = newTileData;
                    extendedData = newExtendedData;
                    effectsData = newEffectsData;
                    newTileData = NULL;
                    newExtendedData = NULL;
                    newEffectsData = NULL;
                }
            }
        }
        catch (...) {
            delete[] newTileData;
            delete[] newExtendedData;
            delete[] newEffectsData;
            throw;
        }
    }
    
    int modified = 0;
    if (xSize != newX) modified |= OBJMOD_WIDTH;
    if (ySize != newY) modified |= OBJMOD_HEIGHT;
    
    xSize = newX;
    ySize = newY;
    
    if (modified) desktop->broadcastObjChange(OBJ_LAYER | modified, this, 0, 0, exWin);
}

void LayerEdit::setTileset(int newTilesetId, Window* srcWin, Window* exWin) throw_File throw_Undo { start_func
    assert(!cached);
    assert(layerType == LAYER_TILE);
    assert(world);

    if (tilesetId != newTilesetId) {
        TileSet* newTileset = NULL;
        setHeaderModified();

        // This is our (undo) exception point
        if (world) getWorldEdit()->undo.storeUndoLayerTileSet(id, tilesetId, srcWin);

        // We have to allow tileset 0 for undo system when deleting a layer
        if (newTilesetId) {
            // We handle the new set first in case it file-excepts, we can go backwards
            newTileset = world->findTileSet(newTilesetId);
            assert(newTileset);
        
            try {
                for (int pos = 0; pos < lockCount; ++pos) {
                    // This is our (file) exception point
                    newTileset->markLock();
                }
            }
            catch (...) {
                if (world) getWorldEdit()->undo.cancelLastUndo();
                throw;
            }  
        }
    
        if (tileset) {
            for (int pos = 0; pos < lockCount; ++pos) {
                tileset->markUnlock();
            }
        }

        tileset = newTileset;
        tilesetId = newTilesetId;

        // Don't broadcast if no tileset; presumably layer is about to be deleted anyways
        if (tileset) {
            desktop->broadcastObjChange(OBJ_LAYER | OBJMOD_TILESET, this, 0, 0, exWin);
        
            // Scan data and set any tiles that are > number of tiles in tileset to 0
            // @TODO: Make this an option, with a popup warning
            clearTilesAbove(tileset->getCount());
        }
    }
}

void LayerEdit::clearTilesAbove(int code) { start_func
    assert(!cached);
    assert(layerType == LAYER_TILE);
    
    int setmod = 0;
    
    if (tileData) {
        for (int pos = xSize * ySize - 1; pos >= 0; --pos) {
            if ((int)(tileData[pos] & LAYER_TILE_INDEX) > code) {
                if (!setmod) setmod = 1;
            
                tileData[pos] = LAYER_TILE_DEFAULT;
                if (extendedData) extendedData[pos] = LAYER_EXT_DEFAULT;
                // (keep effects)
            }
        }
    }
    
    if (setmod) {
        setContentModified();
        // Purposely no exWin as this is a "cascaded" change that may not be expected
        desktop->broadcastObjChange(OBJ_LAYER | OBJMOD_TILES, this, 0, 0);
    }
}

void LayerEdit::addSpawn(SpawnEdit* newSpawn, Window* srcWin, Window* exWin) throw_Undo throw_File { start_func
    assert(!cached);
    assert(newSpawn);

    // Exception points
    if (lockCount) newSpawn->markLock(); 
    try {
        if (world) getWorldEdit()->undo.storeUndoSpawnCreate(newSpawn->getId(), id, srcWin);
    }
    catch (...) {
        if (lockCount) newSpawn->markUnlock(); 
        throw;
    }
    
    setHeaderModified();
    spawns.push_back(newSpawn);
    if (world) getWorldEdit()->indexSpawn(newSpawn);
    desktop->broadcastObjChange(OBJ_SPAWN | OBJMOD_CREATE, newSpawn, 0, 0, exWin);
}

void LayerEdit::deleteSpawn(SpawnEdit* delSpawn, Window* srcWin, Window* exWin) throw_Undo { start_func
    assert(!cached);
    assert(delSpawn);

    vector<Spawn*>::iterator end = spawns.end();
    for (vector<Spawn*>::iterator pos = spawns.begin(); pos != end; ++pos) {
        if (*pos == delSpawn) {
            // Exception point
            int delObj = 1;
            if (world) delObj = !getWorldEdit()->undo.storeUndoSpawnDelete(delSpawn->getId(), id, delSpawn, srcWin);
            if (lockCount) delSpawn->markUnlock();
            setHeaderModified();
            spawns.erase(pos);
            if (world) getWorldEdit()->deindexSpawn(delSpawn);
            desktop->broadcastObjChange(OBJ_SPAWN | OBJMOD_DELETE, delSpawn, 0, 0, exWin);
            if (delObj) delete delSpawn;
            return;
        }
    }
}

int LayerEdit::propertiesDialog(int isNew, LayerType defaultType, Window* srcWin, Window* exWin) { start_func
    int newTileset = tilesetId;
    LayerType newType = layerType;
    string newName = name;
    int newWidth = xSize;
    int newHeight = ySize;
    int newExtended = usesExtended;
    int newEffects = usesEffects;
    // @TODO: allowing type change once created causes errors with scene edit windows
    int allowTypeChange = 0;
    
    if (isNew) {
        newType = defaultType;
        allowTypeChange = 1;
    }

    if (LayerPropertiesDialog::create()->run(allowTypeChange, &newType, &newName, &newWidth, &newHeight, &newTileset, &newExtended, &newEffects, this)) {
        try {
            scene->markLock();
        }
        catch (FileException& e) {
            guiErrorBox(string(e.details), errorTitleFile);
            return 0;
        }

        // Change to new properties
        try {
            if (world) getWorldEdit()->undo.preUndoBlock();
            setName(newName, srcWin, exWin);
            setType(newType, srcWin, exWin);
            setSize(newWidth, newHeight, srcWin, exWin);
            if (newType == LAYER_TILE) {
                setExtended(newExtended, srcWin, exWin);
                setEffects(newEffects, srcWin, exWin);
                try {
                    setTileset(newTileset, srcWin, exWin);
                }
                catch (FileException& e) {
                    if (world) getWorldEdit()->undo.cancelUndoBlock();
                    scene->markUnlock();
                    guiErrorBox(string(e.details), errorTitleFile);
                    return 0;
                }
            }
            if (world) getWorldEdit()->undo.postUndoBlock();
        }
        catch (UndoException& e) {
            scene->markUnlock();
            return 0;
        }
        
        scene->markUnlock();
        return 1;
    }
    return 0;
}

int LayerEdit::isHeaderModified() const { start_func
    return headerModified;
}

int LayerEdit::isContentModified() const { start_func
    return contentModified;
}

void LayerEdit::saveSuccess() { start_func
    headerModified = 0;
    contentModified = 0;
}

void LayerEdit::loadHeader(FileRead* file) throw_File { start_func
    loadHeaderNonSpawns(file);

    int numSpawns = file->readInt();
    if (numSpawns < 0) throw FileException("Corrupted layer header");
    assert(spawns.empty());
    for (; numSpawns > 0; --numSpawns) {
        SpawnEdit* newSpawn = new SpawnEdit(getWorldEdit(), this);
        try {
            newSpawn->load(file, world);
        }
        catch (...) {
            delete newSpawn;
            throw;
        }
        spawns.push_back(newSpawn);
    }
    
    cached = 1;
}

Uint32 LayerEdit::saveHeader(FileWrite* file) throw_File { start_func
    file->writeStr(name);
    file->writeInt(layerType);
    file->writeInt(id);
    
    switch (layerType) {
        case LAYER_EMPTY:
            break;
            
        case LAYER_IMAGE:
        case LAYER_FONT:
            file->writeInt(xSize);
            file->writeInt(ySize);
            break;

        case LAYER_TILE:
            file->writeInt(xSize);
            file->writeInt(ySize);
            file->writeInt(tilesetId);
            file->writeInt(usesExtended);
            file->writeInt(usesEffects);
            break;
        
        default:
            assert(0);
    }
    
    // Spawns for all layer types
    vector<Spawn*>::iterator end = spawns.end();
    file->writeInt(spawns.size());
    for (vector<Spawn*>::iterator pos = spawns.begin(); pos != end; ++pos) {
        dynamic_cast<SpawnEdit*>(*pos)->save(file);
    }

    return 1;
}

void LayerEdit::saveContent(FileWrite* file) throw_File { start_func
    switch (layerType) {
        case LAYER_EMPTY:
        case LAYER_IMAGE: // @TODO:
        case LAYER_FONT: // @TODO:
            break;
            
        case LAYER_TILE:
            assert(tileData);
            file->writeIntBulk(tileData, xSize * ySize);

            if (usesExtended) {
                assert(extendedData);
                file->writeIntBulk(extendedData, xSize * ySize);
            }

            if (usesEffects) {
                assert(effectsData);
                file->writeIntBulk(effectsData, xSize * ySize);
            }
            break;
        
        default:
            assert(0);
    }
}

void LayerEdit::cachedContent(FileRead* file, int oldData) { start_func
    // Should never be called
    assert(0);
}

int LayerEdit::doLock(int play) throw_File { start_func
    if (lockCount == 0) {
        vector<Spawn*>::iterator end = spawns.end();
        vector<Spawn*>::iterator pos = spawns.begin();
        try {
            for (; pos != end; ++pos) {
                assert(*pos);
                SpawnEdit* edit = dynamic_cast<SpawnEdit*>(*pos);
                edit->markLock();
            }
            // Do this in here so exception unlocks spawns
            return Layer::doLock(play);
        }
        catch (...) {
            // (unlock those that were successful)
            for (vector<Spawn*>::iterator pos2 = spawns.begin(); pos2 != pos; ++pos2) {
                SpawnEdit* edit = dynamic_cast<SpawnEdit*>(*pos2);
                edit->markUnlock();
            }
            throw;
        }
    }
    
    return Layer::doLock(play);
}

int LayerEdit::doUnlock(int play) { start_func
    if (lockCount == 1) {
        vector<Spawn*>::iterator end = spawns.end();
        for (vector<Spawn*>::iterator pos = spawns.begin(); pos != end; ++pos) {
            assert(*pos);
            SpawnEdit* edit = dynamic_cast<SpawnEdit*>(*pos);
            edit->markUnlock();
        }
    }
    
    return Layer::doUnlock(play);
}
